# system imports
import os
import sys
import errno
import time
from signal import signal, SIGTERM, SIGHUP


class NoPidFile(Exception):
    def __init__(self, arg):
        super(NoPidFile, self).__init__(arg)
#NoPidFile


class PidFileExists(Exception):
    def __init__(self, arg):
        super(PidFileExists, self).__init__(arg)
#PidFileExists


class BaseDaemon(object):
    """
    A daemon class to manage any independant service process.

    Usage: subclass the BaseDaemon class and override the
    start() and run() methods.
    Put your pre-daemonziation setup code in the overriden start() method.
    For example, bind to a
    socket and create long lived servce objects. The run() method is the entry
    point for service. run() is called after the process has been daemonized
    and is running as an independant service. By default, it will just exit,
    so you *must* override this with your entry point for your service.

    Overriding start() is optional, but it allows you to run code before
    the process has been daemonized and before run() is called. If you
    override start(), you must call the :exc:`super`()'s start() method
    to daemonize the service.

    ===========  ============================================================
    Parameter
    ===========  ============================================================
    *pidfile*    The location for our pid file that this process will create
    *stdin*      File name to set the daemon processes stdin to
    *stdout*     File name to set the daemon processes stdout to
    *stderr*     File name to set the daemon processes stderr to
    ===========  ============================================================

    When a :doc:`basedaemon` instance is started,
    by calling the start() method, it will
    detach itself from the console and run in the background as a service.
    The PID of the service process will be stored in *pidfile*.

    An example of inheriting from :doc:`basedaemon` and
    overriding the start() and run() methods:
    ::
        class ServerDaemon(BaseDaemon):

            def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null',
                 stderr='/dev/null'):
                BaseDaemon.__init__(self, pidfile, stdin, stdout, stderr)
            #__init__()

            def start(self):
                # Setup our listener socket.
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                sock.setblocking(0)
                sock.bind((self.host, self.port))
                sock.listen(128)

                # Create our ioloop instance
                self.io_loop.add_handler(sock.fileno(),
                    self._connection_ready,
                    ioloop.IOLoop.READ | ioloop.IOLoop._EPOLLET)
                self.listen_sock = sock

                # daemonize !!!
                # This will call our run() last thing.
                BaseServer.start(self)
            #start()

            def run(self):
                ioloop.start()
            #run()

        #ServerDaemon

    """

    def __init__(self, pidfile, procname='python_base_daemon', bground=True,
            stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):

        logging.debug("BaseDaemon.__init__()")

        self.pidfile = pidfile
        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        self.procname = procname
        self.bground = bground
    #__init__()

    def _catch_sigterm(self, signum, frame):
        """docstring for _catch_sigterm"""
        logging.debug("BaseDaemon._catch_sigterm")
        self.keep_running = False
        self.delpid()
    #_catch_sigterm()

    def _catch_sighup(self, signum, frame):
        """docstring for _catch_sighup"""
        logging.debug("BaseDaemon._catch_sighup")
        pass
    #_catch_sighup()

    def _write_pid(self):
        logging.debug("BaseDaemon::_write_pid pid file.")
        pid = str(os.getpid())
        open(self.pidfile, 'w+').write("%s\n" % pid)
    #_write_pid()

    def daemonize(self):
        """
        Do the UNIX double-fork magic, see Stevens' "Advanced
        Programming in the UNIX Environment" for details (ISBN 0201563177)

        `ISBN 0201563177 on Amazon <http://www.amazon.com/gp/search/ref=
        sr_adv_b/?search-alias=stripbooks&unfiltered=1&field-keywords=
        &field-author=&field-title=&field-isbn=978-0201563177&field-publisher=
        &node=&field-p_n_condition-type=&field-feature_browse-bin=
        &field-binding_browse-bin=&field-subject=&field-language=
        &field-dateop=&field-datemod=&field-dateyear=&sort=
        relevanceexprank&Adv-Srch-Books-Submit.x=
        -181&Adv-Srch-Books-Submit.y=-192>`_
        """
        logging.debug("BaseDaemon::daemonize %s" % (str(self)))

        try:
            pid = os.fork()
            if pid > 0:
                # exit first parent
                logging.debug("BaseDaemon::daemonize exit first parent")
                sys.exit(0)
        except OSError, e:
            sys.stderr.write("fork #1 failed: %d (%s)\n" %
                (e.errno, e.strerror))
            logging.debug("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
            sys.exit(1)

        # decouple from parent environment
        logging.debug("BaseDaemon::daemonize decouple from parent.")
        os.chdir("/")
        os.setsid()
        os.umask(0)

        # do second fork
        logging.debug("BaseDaemon::daemonize second fork.")
        try:
            pid = os.fork()
            if pid > 0:
                # exit from second parent
                logging.debug("BaseDaemon::daemonize exit second parent")
                sys.exit(0)
            else:
                log.set_proc_name(self.procname)
                proc_name = log.get_proc_name()
                logging.info("Starting %s" % (proc_name))

        except OSError as e:
            sys.stderr.write("pid:%s, fork #2 failed: %s, %s (%s)\n"
                        % (str(pid), e, str(e.errno), e.strerror))
            logging.error("pid:%s, fork #2 failed: %s, %s (%s)\n"
                    % (str(pid), e, str(e.errno), e.strerror))
            sys.exit(1)

        try:
            # redirect standard file descriptors
            logging.debug("BaseDaemon::daemonize redirect file descriptors.")
            sys.stdout.flush()
            sys.stderr.flush()
            si = file(self.stdin, 'r')
            so = file(self.stdout, 'a+')
            se = file(self.stderr, 'a+', 0)
            os.dup2(si.fileno(), sys.stdin.fileno())
            os.dup2(so.fileno(), sys.stdout.fileno())
            os.dup2(se.fileno(), sys.stderr.fileno())

            self._write_pid()

            #logging.debug("BaseDaemon::daemonized, changing to user:%s,
            #    group:%s." % (self.user, self.group))
            ## pwd.getpwnam will raise a KeyError if the user/group
            #   cannot be found.
            #log_uid = pwd.getpwnam(self.user)[2]
            #log_gid = pwd.getpwnam(self.group)[3]
            #os.chmod(self.pidfile, 0644)
            #os.chown(self.pidfile, log_uid, log_gid)
            #os.setgid(log_uid)
            #os.setuid(log_gid)
        except Exception, e:
            logging.error("BaseDaemon::daemonized, Pokemon exception: %s" % e)

        logging.debug("BaseDaemon::daemonized, done.")

    def delpid(self):
        """
        Delete this processes PID file.

        .. note::

            SELinux will not let root delete any processes pid
            file root does not
            own, so *delpid()* will change to the user/group the service is
            set to run as and then delete the pid file.
            This issue occurs when the SELinux security
            context type initrc_var_run_t
            is set on the /var/run directory.
        """

        logging.debug(("BaseDaemon::delpid: deleting %s. uid:%d, gid:%d."
            " file state:%s") %
            (self.pidfile, os.getuid(), os.getgid(), os.stat(self.pidfile)))

        try:
            logging.debug("BaseDaemon::delpid deleting %s" % self.pidfile)
            os.remove(self.pidfile)
        except Exception, e:
            logging.debug(e)
            raise

    def start(self):
        """
        Start the daemon.

        This will see if a pid file already exists,
        if it does it will complain, but
        continue on. Then self.daemonize() and self.run()
        will be called in that order.
        """

        logging.info("BaseDaemon::start")
        # Check for a pidfile to see if the daemon already runs
        try:
            pf = file(self.pidfile, 'r')
            pid = int(pf.read().strip())
            pf.close()
        except IOError:
            pid = None

        if pid:
            message = ("BaseDaemon::start pidfile %s already exist. "
                "BaseDaemon already running?\n")
            logging.error(message % self.pidfile)
            raise PidFileExists(message % self.pidfile)

        if not self.bground:
            logging.info("BaseDaemon::start NOT daemonizing %s" % (str(self)))
            self._write_pid()
            logging.debug("BaseDaemon::start, running in foreground.")
        else:
            # Start the daemon
            self.daemonize()

        # Register our signal handlers
        signal(SIGTERM, self._catch_sigterm)
        signal(SIGHUP, self._catch_sighup)
        self.run()

        #if run exits, clean up.
        #self.stop()

    def stop(self):
        """
        Stop the running daemon represented by this instance.

        First we'll check for a PID file, if doesn't
        exist we'll write a message
        to stdout and exit. If a PID is found, we will try to kill the process
        with the PID by sending it the :exc:`signal`.SIGTERM signal and then
        we will remove the PID file as well.
        """
        logging.debug("BaseDaemon::stop", 'debug')

        # Get the pid from the pidfile
        try:
            pf = file(self.pidfile, 'r')
            pid = int(pf.read().strip())
            pf.close()
        except IOError:
            pid = None

        if not pid:
            message = "pidfile %s does not exist. BaseDaemon not running?\n"
            sys.stderr.write(message % self.pidfile)
            raise NoPidFile("pid file %s not found" % (self.pidfile))

        # Try killing the daemon process
        try:
            while 1:
                os.kill(pid, SIGTERM)
                time.sleep(0.1)
            except OSError, err:
                err = str(err)
                if err.find("No such process") > 0:
                    if os.path.exists(self.pidfile):
                        self.delpid()
            else:
                print str(err)
                sys.exit(1)

    def restart(self):
        """
        Restart the daemon.

        This just calls start and stop:
        ::

            self.stop()
            self.start()

        """
        logging.info("BaseDaemon::restart", 'info')
        self.stop()
        self.start()

    def run(self):
        """
        Starts the zero mq loop.
        Use the stop() method to stop this process.
        """

        logging.debug("BaseDaemon::run bailing out immediately", 'debug')
#BaseDaemon
